package tul.cot.util;

import java.util.List;

import org.apache.commons.lang.ArrayUtils;

public class StatisticsUtils {
	
	public static class HpdRange {
		public double min,max;
		private double range;
		
		public HpdRange() {
			this.min = 0;
			this.max = 0;
			this.range = Double.MAX_VALUE;
		}
		
		public HpdRange(double min, double max) {
			set(min, max, max - min);
		}

		public void set(double min, double max, double range) {
			this.min = min;
			this.max = max;
			this.range = range;
		}
		
		
		
		
	}
	
	/**
	 * Sequence with head
	 */
	private static class HSeq {
		
		private final double[] values;
		private final int headSize;
		private final double headValue;
		
		private final int size;
		
		public HSeq(double[] values, int headSize, double headValue) {
			this.values = values;
			this.headSize = headSize;
			this.headValue = headValue;
			
			this.size = values.length + headSize;
		}
		
		public int size() {
			return size;
		}
		
		/**
		 * @param idx index [0 .. size()-1]
		 * @return
		 */
		public double getValue(int idx) {
			if (idx >= size)
				throw new IllegalArgumentException(String.format("Index out of range [idx(%d) >= size(%d)]", idx, size));
			
			if (idx < headSize)
				return headValue;
			
			return values[idx - headSize];
		}
	}
	
	public static double getPrecentile(double prec, double[] sortedValues, int headSize, double headValue) {
		
		if (headSize < 0) {
			throw new IllegalArgumentException("headSize < 0");
		}
		
		if (sortedValues.length == 0 && headSize == 0) {
			throw new IllegalArgumentException("sortedValues.length == 0 && headSize == 0");
		}
		
		if (headSize > 0 && sortedValues.length > 0 && headValue > sortedValues[0]) {
			throw new IllegalArgumentException("headSize > 0 && sortedValues.length > 0 && headValue > sortedValues[0]");
		}
		
		HSeq seq = new HSeq(sortedValues, headSize, headValue);
		
		int id = getPrecentileIndex(prec, 0, seq.size() - 1);
		
		return seq.getValue(id);
	}
	
	public static double getPrecentile(double prec, List<Double> sortedValues, int headSize, double headValue) {
		Double[] arrayObj = sortedValues.toArray(new Double[sortedValues.size()]);
		double[] array = ArrayUtils.toPrimitive(arrayObj);
		
		return getPrecentile(prec, array, headSize, headValue);
	}
	
	public static double getMedian(double[] sortedValues) {
		return getMedian(sortedValues, 0, 0);
	}
	
	public static double getMedian(double[] sortedValues, int headSize, double headValue) {
//		return getPrecentile(50, sortedValues, headSize, headValue);
		
		HSeq seq = new HSeq(sortedValues, headSize, headValue);
		return getMedian(seq);
	}
	
	public static double getMedian(List<Double> sortedValues) {
		return getMedian(sortedValues, 0, 0);
	}
	
	public static double getMedian(List<Double> sortedValues, int headSize, double headValue) {
//		return getPrecentile(50, sortedValues, headSize, headValue);
		
		return getMedian(getArray(sortedValues), headSize, headValue);
	}
	
	private static double[] getArray(List<Double> values) {
		return ArrayUtils.toPrimitive(values.toArray(new Double[]{}));
	}
	
	private static double getMedian(HSeq seq) {
		return average(seq, getMedianIdxs(seq));
	}
	
	private static int[] getMedianIdxs(HSeq seq) {
		if (seq.size() % 2 == 0) {
			return new int[] {seq.size()/2-1, seq.size()/2};
		} else {
			return new int[] {seq.size()/2};
		}
	}
	
	private static double average(HSeq seq, int[] idxs) {
		double sum = 0;
		for (int i: idxs) {
			sum += seq.getValue(i);
		}
		
		return idxs.length == 1 ? sum : sum/idxs.length;
	}
	
	/**
	 * @param prec (0, 100]
	 * @param minIdx
	 * @param maxIdx
	 * @return
	 */
	public static int getPrecentileIndex(double prec, int minIdx, int maxIdx) {
		if (minIdx > maxIdx) {
			throw new IllegalArgumentException("minIdx > maxIdx"); 
		}
		
		final int N = maxIdx - minIdx + 1;
		double d = ((double)N) * prec / 100.0;
		
		int idx = (int) (Math.ceil(d + minIdx - 1));
		
		if (idx < minIdx || idx > maxIdx) {
			throw new RuntimeException(String.format("Computed id(%d) out of limits(%d, %d)", idx, minIdx, maxIdx));
		}
		
		return idx;
	}

	public static HpdRange getHpd(double confidence, double[] sortedValues, int headSize, int headValue) {
		
		if (headSize < 0) {
			throw new IllegalArgumentException("headSize < 0");
		}
		
		if (sortedValues.length == 0 && headSize == 0) {
			throw new IllegalArgumentException("sortedValues.length == 0 && headSize == 0");
		}
		
		if (headSize > 0 && sortedValues.length > 0 && headValue > sortedValues[0]) {
			throw new IllegalArgumentException("headSize > 0 && sortedValues.length > 0 && headValue > sortedValues[0]");
		}
		
		HSeq seq = new HSeq(sortedValues, headSize, headValue);
		final int N = seq.size();
		
		final int confN = (int) Math.ceil(N * confidence);
		if (confN == 0)
			throw new IllegalArgumentException("Confidence level to small");

		HpdRange hpd = new HpdRange();
		for (int i=0; i+confN-1 < N; ++i) {
			int j = i + confN - 1;
			
			double min = seq.getValue(i);
			double max = seq.getValue(j);
			double range = max - min;
			
			if (range < hpd.range) {
				hpd.set(min, max, range);
			}
		}
			
		
		return hpd;
	}
	
	public static double getDAI(int cnt1, int cnt2) {
		int sum = cnt1 + cnt2;
		
		return (sum > 0) ? ((double)cnt1) / ((double)sum) * 2.0 - 1.0
						 : HistogramAggregator.DUMMY_DAI_VALUE;
	}
}
